import React from 'react'
import classNames from 'classnames'
import * as PropTypes from 'prop-types'
import {observer} from 'mobx-react'
import Common from '@/lib/Common';
import {Icon} from '../Icon'
import HtmlParser from 'react-html-parser'
import {ErrorMessage} from '@/components/Validation'
import {StoreManager} from "@/stores";
import {SelectStore} from "@/components/Select";

@observer
export class Select extends React.Component {

    static contextTypes = {
        formSettings: PropTypes.object
    }

    static propTypes = {
        name: PropTypes.oneOfType([PropTypes.string, PropTypes.symbol]).isRequired,
        disabled: PropTypes.bool,
        validations: PropTypes.arrayOf(PropTypes.func),
        placeholder: PropTypes.string,
        searchPlaceholder: PropTypes.string,
        options: PropTypes.oneOfType([PropTypes.array, PropTypes.func]),
        showSearch: PropTypes.bool,
        render: PropTypes.func
    }

    static defaultProps = {
        validations: [],
        searchPlaceholder:'请输入搜索关键字',
        showSearch: true,
        showErrors: true
    }

    constructor(props, context) {
        super(props, context)
        const parent = context.formSettings && context.formSettings.store
        this.store = StoreManager.create(new SelectStore(props), parent)
    }

    componentWillUnmount() {
        StoreManager.delete(this.store)
        this.handleCollaspe()
    }

    scrollToIndex(selectedIndex, direction = 'up') {
        if (selectedIndex === -1) {
            return
        }
        this.$result = this.$result || this.refs.select.querySelector('.select-results')
        const $result = this.$result
        if (!$result.children || $result.children.length <= selectedIndex) {
            return
        }
        const $item = $result.children[selectedIndex]
        const offsetTop = $item.offsetTop - $result.scrollTop
        const itemHeight = $item.offsetHeight
        if (direction === 'down') {
            if (offsetTop >= $result.offsetHeight - itemHeight) {
                $result.scrollTop = $item.offsetTop - $result.offsetHeight + itemHeight * 2
            }
        } else {
            if (offsetTop <= itemHeight) {
                $result.scrollTop = $item.offsetTop - itemHeight
            }
        }
    }

    handleSearchKeydown = (event) => {
        event.persist();
        if ([38, 40, 13].indexOf(event.keyCode) !== -1) {
            event.preventDefault()
            event.stopPropagation()
        }
        const {selectedIndex} = this.store
        if (event.keyCode === 40) {//Down
            this.scrollToIndex(selectedIndex, 'down')
            this.store.selectNext()
        } else if (event.keyCode === 38) {//Up
            this.store.selectPrev()
            this.scrollToIndex(selectedIndex)
        } else if (event.keyCode === 13) { //Enter
            this.selectByIndex(selectedIndex)
        }
    }

    handleSearchInputChange = (event) => {
        event.persist();
        this.filterByKeyword(event.target.value)
    }

    handleSearchBlur = (event) => {
        event.persist();
        setTimeout(() => {
            this.handleToggle()
            if (this.context.formSettings.showErrorsOnBlur) {
                this.store.validate()
            }
        }, 200)
    }

    handleSelectedIndexChange(option, selectedIndex) {
        this.select(option, selectedIndex)
    }

    handleOver(event) {
        const selectDom = this.refs.select
        if (event.path.findIndex(t => t === selectDom) === -1) {
            this.handleCollaspe()
        }
    }

    handleCollaspe() {
        this.store.collaspe()
        this.isSearchInputFocus = false
        document.removeEventListener('scroll', this.correctionDirection)
        document.removeEventListener('click', this.handleOver)
    }

    handleExpand() {
        if (this.isSearchInputFocus) {
            return
        }
        const overPage = Common.isOverPage(this.refs.select, '.select-drop')
        this.scrollToIndex(this.store.selectedIndex)
        this.store.expand(overPage)
        this.isSearchInputFocus = true
        document.addEventListener('scroll', this.correctionDirection.bind(this), {passive: true})
        document.addEventListener('click', this.handleOver.bind(this))
    }

    handleToggle() {
        this.store.isExpanded ? this.handleCollaspe() : this.handleExpand()
    }

    filterByKeyword = Common.debounce(keyword => {
        const store = this.store
        if (!keyword || keyword === '') {
            store.clearFiltered()
            return
        }
        const results = []
        store.options.forEach(item => {
            const {label, value} = item
            if (new RegExp(`(${keyword})`, 'ig').test(label)) {
                results.push({
                    highlightLabel: HtmlParser(item.label.replace(new RegExp(`(${keyword})`, 'ig'), `<em>$1</em>`)),
                    label,
                    value
                })
            }
        })
        store.changeFilteredOptions(results)
    }, 100)

    correctionDirection = Common.debounce(() => {
        if (!this.refs.select) {
            return
        }
        this.store.changeOverPage(Common.isOverPage(this.refs.select, '.select-drop'))
    }, 100)

    selectByIndex(index) {
        const option = this.store.currentOptions[index]
        if (option) {
            this.select(option, index)
        } else {
            console.warn('Select选择项失败,索引超出范围')
        }
    }

    select(option, selectedIndex = -1) {
        if (selectedIndex === -1) {
            selectedIndex = this.store.currentOptions.indexOf(option)
        }
        this.store.changeSelected(option.label, option.value, selectedIndex)
        if (this.context.formSettings.showErrorsOnChange) {
            this.store.validate()
        }
        this.handleCollaspe()
    }

    /**
     * 渲染搜索部分
     * @returns {*}
     */
    renderSelectSearch() {
        const {
            showSearch,
            searchPlaceholder
        } = this.props
        if (!showSearch) {
            return null
        }
        return (
            <div className="select-search">
                <input type="text"
                       autoComplete="off"
                       placeholder={searchPlaceholder}
                       onChange={this.handleSearchInputChange}
                       onKeyDown={this.handleSearchKeydown}
                       onBlur={this.handleSearchBlur}
                       autoFocus={true}/>
                <Icon type="search"/>
            </div>)
    }

    renderSelectChosen() {
        const {
            placeholder
        } = this.props
        const {
            selectedText,
            value
        } = this.store
        const className = classNames({'placehodler': value === undefined, 'selected-item': !!value})
        return (
            <div className="select-chosen" onClick={this.handleToggle.bind(this)}>
                <span className={className}>{value === undefined ? placeholder : selectedText}</span>
                <i className="select-caret"></i>
            </div>)
    }

    renderSelectResults() {
        const {
            render,
        } = this.props
        const {
            currentOptions,
            selectedIndex
        } = this.store
        return (
            <ul className="select-results">
                {currentOptions.map((option, index) => {
                    return (
                        <li key={option.value}
                            onClick={this.handleSelectedIndexChange.bind(this, option, index)}
                            className={classNames({selected: index === selectedIndex})}>
                            {render ? render(option) :
                                <span>{option.highlightLabel || option.label}</span>}
                        </li>)
                })}
            </ul>)
    }

    render() {
        const {
            showErrors,
            className,
        } = this.props
        const {
            isOverPage,
            isExpanded,
            invalid,
            errors,
        } = this.store
        return (
            <div className={classNames('select', {dropup: isOverPage, expanded: isExpanded}, className)} ref="select">
                {this.renderSelectChosen()}
                <div className={classNames('select-drop', this.store.classNames)}>
                    {this.renderSelectSearch()}
                    {this.renderSelectResults()}
                </div>
                <ErrorMessage showErrors={showErrors} invalid={invalid} errors={errors}/>
            </div>)
    }
}

